package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.db.UAFDAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.db.dao.UasConfigDAO;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFDeregisterProcessor;
import com.gmrz.uaf.protocol.v1.processor.UAFRegInitProcessor;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertRegInitProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPRegInitProcessor;
import com.gmrz.uaf.server.ServerConfig;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.ReceiveReqPayload;
import com.gmrz.uaf.servlet.v1.request.RegContext;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.gmrz.util.db.DBUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public class UAFRegRequestHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(UAFRegRequestHandler.class);

    /**
     * {
     * "context":
     * {
     * "appID":"default",
     * "transNo":"asdfghjklqwertyuiopzxcvbnmaasdf",
     * "userName":"whz",
     * "deviceID":"+xkVOHTXVzFOpP+YzyHsZhFbn4ldgiWXGZ\/eGL5NHB7hXTn46LeqOPfYQk+YO9QL",
     * "transType":"01",
     * "authType":"00"
     * }
     * }
     *
     * @param request
     * @return {
     * "statusCode":1200,
     * "description":"OK",
     * "uafRequest":"[
     * "[{\"header\":{....},\"policy\":{.....}}]"
     * ]"
     * }
     */
    public String regInit(String request) {

        LOG.info("[/reg/receive][begin]");
        // 将请求数据放入到FidoLog的request属性中，用于后面的日志记录
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        // 使用UAFSchemaBuilder创建的gson实例，这样在进行序列化或反序列化时，@Expose注解才会生效
        Gson gson = UAFSchemaBuilder.getGson();
        FidoRespPayload<String> respPayload = new FidoRespPayload<String>();
        // 这是使用gson将json串转为对象时转换的类型
        Type reqType = new TypeToken<ReceiveReqPayload<RegContext>>() {
        }.getType();

        String response;
        try {
            // 使用gson将request请求串转为ReceiveReqPayload<RegContext>对象
            ReceiveReqPayload<RegContext> reqPayload = gson.fromJson(request, reqType);
            // 兼容 旧接口的deviceID
            if (Strings.isNullOrEmpty(reqPayload.getContext().getDeviceID())) {
                reqPayload.getContext().setDeviceID(reqPayload.getContext().getDevices().getDeviceID());
            }
            // 记录日志
            logRequest(request, reqPayload.getContext().getTransNo(), reqPayload.getContext().getUserName(),
                    reqPayload.getContext().getAppID(), reqPayload.getContext().getAuthType(), reqPayload.getContext().getTransType(), Constants.Method.REG_INIT);
            // 校验注册接口的状态
            checkUafRegStatus();

            // 如果appid是“9999”的话，将“Perform”设置为true，默认是false
            ServerContext.getServerContext().setPerform(reqPayload.getContext().getAppID());
            ServerContext.getServerContext().isPerform();
            ServerContext.getServerContext().setBase64(reqPayload.getContext().isBase64());

            if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_4 && !ServerContext.getServerContext().isPerform()) {
                // 验证白名单和黑名单
                validateDevice(reqPayload.getContext().getDevices(), reqPayload.getContext().getAppID());
            }
            if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !ServerContext.getServerContext().isPerform()) {
                // 验证context参数
                validate(reqPayload);
            }
            UAFRegInitProcessor processor = null;
            // 根据认证方式选择具体的Processor业务处理器
            if (reqPayload.getContext().getAuthType().compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {
                // 走证书
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPCertRegInitProcessor.class);
            } else if (reqPayload.getContext().getAuthType().compareTo(Constants.AuthType.REMOTE_FACEPRINT.getAuthType()) >= 0) {
                // 走远程
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPRegInitProcessor.class);
            } else {
                // 走本地的指纹、人脸等
                processor = GuiceUtil.getProcessorInjector().getInstance(UAFRegInitProcessor.class);
            }
            RegContext context = reqPayload.getContext();
            // hash 用户名，使用 SHA-1 摘要算法对用户信息进行摘要计算，然后在将摘要转为16进制字符串
            String hashedUserName = generateHashedUserName(context.getUserName(), context.getRf1(), context.getRf2());
            // 原用户名
            String custNo = context.getUserName();
            byte[] challengeByte = new byte[0];
            String challenge = context.getChallenge();
            if (Strings.isNotBlank(challenge)) {
                challengeByte = challenge.getBytes("utf-8");
            }
            // 使用processor.request（）方法进行请求的业务处理
            List<RegistrationRequest> requests = processor.request(custNo, hashedUserName, context.getAppID(),
                    context.getDeviceID(), context.getAuthType(), context.getTransType(), challengeByte,context.getDn());
            // 用户名是明文返回还是密文返回
            if (!GuiceUtil.getProcessorInjector().getInstance(ServerConfig.class).isSecurity()) {
                for (RegistrationRequest rr : requests) {
                    rr.setUsername(context.getUserName());
                }
            }
            if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !ServerContext.getServerContext().isPerform()) {
                // 缓存发起接口的 context 参数
                saveContextToRedis(reqPayload, requests, hashedUserName);
            }
            // 判断是否是  强制注册
            if (!Strings.isNullOrEmpty(context.getOpType()) && Constants.OPType.REGIST_SIGNLE.getOpType().equals(context.getOpType())) {
                UAFDeregisterProcessor deregisterProcessor = GuiceUtil.getProcessorInjector()
                        .getInstance(UAFDeregisterProcessor.class);
                // 查询用户已经注册成功的认证数据
                Set<Authenticator> authenticators = deregisterProcessor.getAuthenticatorsOnOneDeviceDEL(hashedUserName, context.getAppID(), context.getDeviceID(), context.getAuthType(), context.getTransType(),
                        Constants.AuthenticatorStatus.ACTIVE.getStatus());
                if (null != authenticators && !authenticators.isEmpty()) {
                    // 走一遍注销
                    DeregisterationRequest deregisterationRequest = deregisterProcessor.deleteOne(hashedUserName, context.getAppID(),
                            context.getDeviceID(), context.getAuthType(), context.getTransType(), "00",Constants.CANCEL_REASON_TYPE_2);
                    if (null != deregisterationRequest) {
                        List<DeregisterAuthenticator> deregList = deregisterationRequest.getDeregisterAuthenticators();
                        if (null != deregList && !deregList.isEmpty()) {
                            for (DeregisterAuthenticator deregisterAuthenticator : deregList) {
                                String aaid = deregisterAuthenticator.getAaid().toString();
                                // 已经删除的认证器数据如果在“策略中的disallowed”中的话，则将其从“策略中的disallowed”移除，使其能够可以再次进行注册
                                for (RegistrationRequest registrationRequest : requests) {
                                    List<MatchCriteria> disallowed = registrationRequest.getPolicy().getDisallowed();
                                    if (null != disallowed && !disallowed.isEmpty()) {
                                        Iterator<MatchCriteria> it = disallowed.iterator();
                                        while (it.hasNext()) {
                                            MatchCriteria matchCriteria = it.next();
                                            String matchCriteriaAaid = matchCriteria.getAAID().get(0).toString();
                                            if (aaid.trim().equals(matchCriteriaAaid.trim())) {
                                                it.remove();
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }else if (Strings.isNotBlank(context.getOpType()) && Constants.OPType.REGIST_RESPONSE_NO_KEYID.getOpType().equals(context.getOpType())) {
                for (RegistrationRequest registrationRequest : requests) {
                    List<MatchCriteria> disallowed = registrationRequest.getPolicy().getDisallowed();
                    if (null != disallowed && !disallowed.isEmpty()) {
                        Iterator<MatchCriteria> it = disallowed.iterator();
                        while (it.hasNext()) {
                            MatchCriteria matchCriteria = it.next();
                            it.remove();
                        }
                    }
                }
            }
            response = buildReceiveResponse(gson, respPayload, requests);
        } catch (Exception ex) {
            LOG.error("[/reg/receive] ERROR", ex);
            response = buildFidoRespFromException(respPayload, ex);
        }
        LOG.info("[/reg/receive][end]");
        return response;
    }

    /**
     * 缓存发起接口的 context 参数
     *
     * @param reqPayload
     * @param requests
     * @param hashedUserName
     * @throws ChallengeStoreException
     */
    private void saveContextToRedis(ReceiveReqPayload<RegContext> reqPayload, List<RegistrationRequest> requests, String hashedUserName) throws ChallengeStoreException {
        String challenge = Convert.toBase64(requests.get(0).getHeader().getServerData().getChallenge());

        RegContext context = reqPayload.getContext();
        // 将关键数据拼接成字符串，用于放入到缓存中
        String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                context.getAppID(), context.getDeviceID(), context.getTransType(), context.getAuthType(), context.getChallenge());
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        challengeStoreService.setRedisValue(Constants.UAP_REQ_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName, 300, param);
    }

    /**
     * 验证context参数
     *
     * @param reqPayload
     * @throws PayloadValidationException
     */
    private void validate(ReceiveReqPayload<RegContext> reqPayload)
            throws PayloadValidationException, UnsupportedEncodingException {
        if ((reqPayload == null) || (reqPayload.getContext() == null)) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        RestValidator.validate("userName", reqPayload.getContext().getUsername());
        RestValidator.validate("appID", reqPayload.getContext().getAppID());
        RestValidator.validate("deviceID", reqPayload.getContext().getDeviceID());
        RestValidator.validate("authType", reqPayload.getContext().getAuthType());
        RestValidator.validate("transType", reqPayload.getContext().getTransType());
        RestValidator.validate("transNo", reqPayload.getContext().getTransNo());
        RestValidator.validate("rf1", reqPayload.getContext().getRf1());
        RestValidator.validate("rf2", reqPayload.getContext().getRf2());
        RestValidator.validate("dn", reqPayload.getContext().getDn());
        //RestValidator.validate("opType", reqPayload.getContext().getOpType());
        String opType = reqPayload.getContext().getOpType();
        if (null != opType) {
            if (opType.length() != 2) {
                throw new PayloadValidationException("opType", RestValidator.VALIDATION_ERROR.INVALID_LENGTH);
            }
        }
        RestValidator.validateDevices(reqPayload.getContext().getDevices());
        String challenge = reqPayload.getContext().getChallenge();
        if (Strings.isNotBlank(challenge)) {
            if (challenge.getBytes("utf-8").length > 32) {
                throw new PayloadValidationException("challenge", RestValidator.VALIDATION_ERROR.INVALID_LENGTH);
            }
        }

        if (reqPayload.getContext().getAuthType().compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {
            String dtStr = reqPayload.getContext().getDevices().getDeviceID().substring(0,2);
            LOG.debug("dtStr:"+dtStr);
            Connection conn = null;
            try {
                conn = UAFDBConnectionMgr.getConnection();
                UasConfigDAO uasConfigDAO = UAFDAOFactory.createUasConfigDAO(conn);
                List<String> certDeviceType = uasConfigDAO.getUserConfig(Constants.CERT_DEVICE_TYPE);
                List<String> certDeviceTypeUpperCase = new ArrayList<String>();
                for(String cdt : certDeviceType){
                    certDeviceTypeUpperCase.add(cdt.toUpperCase());
                }
                if(!certDeviceTypeUpperCase.contains(dtStr.toUpperCase())){
                    throw new PayloadValidationException("deviceID", RestValidator.VALIDATION_ERROR.INVALID_CHARACTERS);
                }
            } catch (Exception e) {
                LOG.error("compare deviceId error", e);
                throw new PayloadValidationException("deviceID", RestValidator.VALIDATION_ERROR.INVALID_CHARACTERS);
            }finally {
                DBUtil.close(conn);
            }
        }
    }
}
